package me.upyun

import scalaj.http.Http
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
import java.util.Date
import scalaj.http.HttpOptions
import scala.collection.mutable.Map
import java.lang.Boolean
import java.io.File
import java.io.FileInputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths


class UpYun(bucket:String, operator: String, password: String, hostPre:String = "v0") {
    
    /**
     * 初始化
     */
    val host = hostPre + ".api.upyun.com"  // 生成接入点

    /**
     * 求Array[Byte]类型变量s的md5值，32位16进制 <br>
     * 
     * @param s Array[Byte]类型
     * @return md5结果的字符串表示 
     */
    private def md5(s:Array[Byte]) = {
      val md5Array = MessageDigest.getInstance("MD5").digest(s)
      val sb = new StringBuffer();
      for (a <- md5Array) {
          sb.append(Integer.toHexString((a & 0xFF) | 0x100).substring(1,3));
       }
      sb.toString()
    }
    
    /**
     * 获取GMT类型的时间
     * 
     * @return 字符串类型的时间
     */
    private def getDate() = {
      val formater = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US)
      formater.setTimeZone(TimeZone.getTimeZone("GMT"))
      formater.format(new Date())
    }

    /**
     * 读取本地文件，byte形式<br>
     * 
     * @param path:String
     * 本地文件路径
     * 
     * @return 
     * 文件内容，Array[Byte]类型
     */
    def readLocalFile(path:String) = {
        val filePath = Paths.get(path)
        Files.readAllBytes(filePath)
    }
    
    /**
     * 将云端文件内容，保存到本地
     * 
     * @param path:String
     * 本地路径名，文件保存到这个路径中
     * @param data:Array[Byte]
     * 文件内容，使用get(path)获得
     * 
     * @return 
     * 没有返回内容
     */
    def writeFile2Local(path:String, data:Array[Byte]) {
        val filePath = Paths.get(path)
        Files.write(filePath, data)
    }
    
    /**
     * 生成signature
     * 
     * @param method:String
     * "PUT"、"POST"、"HEAD"、"DELETE"等方法
     * @param path:String
     * 访问路径
     * @param date:String
     * GMT类型的时间
     * @param contentLen:Int
     * http请求主体的长度
     * 
     * @return 
     * md5结果
     */
    private def getSignature(method:String, path:String, date: String, contentLen:Int = 0) = {
        val s = method + "&" + "/" + bucket + path + "&" + date +"&"+ contentLen + "&" + md5(password.getBytes)
        md5(s.getBytes)
    }
    
    /**
     * 获取云文件/目录的信息，若没有，则抛出异常
     * 
     * @param path:String
     * 云端文件路径
     * 
     * @return 
     * Map[String, String]()类型
     * 文件信息("file-type"->?,"file-size"->?,"file-date"->?)
     */
    def getInfo(path:String) = {
         val method = "HEAD"
         val date = getDate()
         val signature = getSignature(method, path, date)
         val authorization = "UpYun "+operator+":"+signature
         val request = Http("http://" + host + "/" + bucket + path ).method("HEAD")
             .header("Host", host)
             .header("Authorization", authorization)
             .header("Date", date)
             .header("Content-Length", "0")
             .option(HttpOptions.connTimeout(1000)).option(HttpOptions.readTimeout(5000))
         val (responseCode, headersMap, resultString) = request.asHeadersAndParse(Http.readString)
         
         if (responseCode != 200) {
           throw new Exception("error, cannot get the info of "+path)
         }
         
         val file_type = headersMap("x-upyun-file-type")(0)
         
         val result = Map[String, String]()
         
         if (file_type == "folder") {
           result += ("file-type" -> file_type, 
               "file-date"-> "0",
               "file-size" -> "0")
         }
         if (file_type == "file") {
           result += ("file-type" -> file_type, 
               "file-date" -> headersMap("x-upyun-file-date")(0),
               "file-size" -> headersMap("x-upyun-file-size")(0))
         }
         result
    }
    
    /**
     * 判断是否为目录，是则返回true
     * 
     * @param path:String
     * 云端文件路径
     */
    def isDir(path: String) = {
        val info = getInfo(path)
        if (info("file-type") == "folder") true else false
    }
    
    /**
     * 判断是否为文件
     * 
     * @param path:String
     * 云端文件路径
     */
    def isFile(path: String) = {
        val info = getInfo(path)
        if (info("file-type") == "file") true else false
    }  
    
    /**
     * 获取某个目录下的文件列表
     * path必须是目录，否则返回的是文件内容
     * 
     * @param path:String
     * 云端文件路径
     */
    def getList(path:String) = {
         val info = getInfo(path)
         if (info("file-type") == "file") {
             throw new Exception("error, "+path+" is file, not directory")
         }
		 val method = "GET"
		 val date = getDate()
		 val signature = getSignature(method, path, date)
		 val authorization = "UpYun "+operator+":"+signature
		 
		 val request = Http("http://" + host + "/" + bucket + path ).method("GET")
		     .header("Host", host)
		     .header("Authorization", authorization)
		     .header("Date", date)
		     .header("Content-Length", "0")
		     .option(HttpOptions.connTimeout(1000)).option(HttpOptions.readTimeout(5000))
		 val (responseCode, headersMap, resultString) = request.asHeadersAndParse(Http.readString)
		 
		 var result = List[Map[String, String]]()
		 val splitResponse = resultString.split("\n")
		 
		 for (arg1 <- resultString.split("\n")) {
		   val fileInfo = arg1.split("\t")
		   result = result ::: List( Map[String, String](
		       "file-name"->fileInfo(0), 
		       "file-type"->{if (fileInfo(1)=="F") "folder" else "file"}, 
		       "file-size"->fileInfo(2), 
		       "file-date"->fileInfo(3)))
		 }    
		 
		 result
    }
    
    /**
     * 获取空间使用情况
     * 
     * @return
     * 空间使用量
     */
    def getUsage() = {
         val method = "GET"
         val date = getDate()
         val signature = getSignature(method, "/?usage", date)
         val authorization = "UpYun "+operator+":"+signature
         val request = Http("http://" + host + "/" + bucket + "/?usage" ).method("GET")
             .header("Host", host)
             .header("Authorization", authorization)
             .header("Date", date)
             .header("Content-Length", "0")
             .option(HttpOptions.connTimeout(1000)).option(HttpOptions.readTimeout(5000))
         val (responseCode, headersMap, resultString) = request.asHeadersAndParse(Http.readString)
         resultString
    }
    
    /**
     * 创建目录，默认不支持级联创建
     * 
     * @param path:String
     * 云端路径
     * @param cascade:Boolean
     * 是否允许创建多级目录
     * 
     * @return true or false
     * 创建是否成功
     */
    def mkdir(path:String, cascade:Boolean = false) = {
         val method = "POST"
         val date = getDate()
         val signature = getSignature(method, path, date)
         val authorization = "UpYun "+operator+":"+signature
         val request = Http.post("http://" + host + "/" + bucket + path)
             .header("Host", host)
             .header("Authorization", authorization)
             .header("Date", date)
             .header("Content-Length", "0")
             .header("Folder", "true")
             .header("mkdir", cascade.toString())
             .option(HttpOptions.connTimeout(1000)).option(HttpOptions.readTimeout(5000))
         val (responseCode, headersMap, resultString) = request.asHeadersAndParse(Http.readString)
         if (responseCode == 200) true else false
    }
    
    /**
     * 删除目录/文件；只能删除空目录？
     * 
     * @param path:String
     * 云端路径
     * 
     * @return true or false
     * 是否删除成功
     */
    def delete(path:String) = {
         val method = "DELETE"
         val date = getDate()
         val signature = getSignature(method, path, date)
         val authorization = "UpYun "+operator+":"+signature
         val request = Http("http://" + host + "/" + bucket + path).method(method)
             .header("Host", host)
             .header("Authorization", authorization)
             .header("Date", date)
             .header("Content-Length", "0")
             .option(HttpOptions.connTimeout(1000)).option(HttpOptions.readTimeout(5000))
         val (responseCode, headersMap, resultString) = request.asHeadersAndParse(Http.readString)
         if (responseCode == 200) true else false
    }
    
    /**
     * 获取文件内容
     * 
     * @param path:String
     * 云端路径
     * 
     * @return
     * (Boolean, Array[Byte])
     */
    def get(path:String) = {
        val method = "GET"
        val date = getDate()
        val signature = getSignature(method, path, date)
        val authorization = "UpYun "+operator+":"+signature
        val request = Http("http://" + host + "/" + bucket + path).method(method)
             .header("Host", host)
             .header("Authorization", authorization)
             .header("Date", date)
             .header("Content-Length", "0")
             .option(HttpOptions.connTimeout(1000)).option(HttpOptions.readTimeout(5000))
        val (responseCode, headersMap, resultBytes) = request.asHeadersAndParse(Http.readBytes)
        (if (responseCode==200) true else false, resultBytes)
    }
    
    /**
     * 上传data到文件path,如果是图片，请用imgConfig参数设置一些操作参数
     * 
     * @param path:String
     * 云端路径
     * @param data:Array[Byte]
     * 数据
     * 
     * 
     */
    def put(path:String, data:Array[Byte], 
            imgConfig:Map[String, String] = Map[String, String](),
            mkdir:Boolean = false, contentMD5:Boolean = false,
            contentSecret:String = null, contentType:String = null) = {
        val method = "POST"
        val date = getDate()
        val signature = getSignature(method, path, date, data.length)
        val authorization = "UpYun "+operator+":"+signature
        val request = Http.postData("http://" + host + "/" + bucket + path, data)
             .header("Host", host)
             .header("Authorization", authorization)
             .header("Date", date)
             .header("Content-Length", data.length.toString)
             .header("mkdir", mkdir.toString)
             .option(HttpOptions.connTimeout(1000)).option(HttpOptions.readTimeout(5000))
             
        if (contentMD5) request.header("Content-MD5", md5(data))
        if (contentSecret != null) request.header("Content-Secret", contentSecret)
        if (contentType != null) request.header("Content-Type", contentType)
             
        for (arg <- imgConfig) {
            //println(arg)
            request.header(arg._1 , arg._2)
        }
        val (responseCode, headersMap, resultString) = request.asHeadersAndParse(Http.readString)
        val info = Map[String, String]()
        if (headersMap.keySet.contains("x-upyun-width")) {
            info += ("width"-> headersMap("x-upyun-width")(0))
            info += ("height"->headersMap("x-upyun-width")(0))
            info += ("frames"->headersMap("x-upyun-frames")(0))
            info += ("file-type"->headersMap("x-upyun-file-type")(0))
        }
        
        (if (responseCode==200) true else false, info)
    }
}